﻿/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace IvyOrm
{
    class IvyClassInfo
    {
        public static IvyClassInfo GetClassInfo(Type targetType)
        {
            Debug.Assert(!targetType.IsArray); //It should be impossible to get to this as arrays do not satisfy the New() condition

            if (targetType.IsPrimitive || targetType.IsEnum)
                throw new ArgumentOutOfRangeException("targetType", "Only supported with struct and class");
            

            var resultInfo = new IvyClassInfo();

            TableAttribute tableAttribute = (targetType.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault() as TableAttribute) ?? new TableAttribute();
            bool optIn = (tableAttribute.Options & TableOptions.ColumnsOptIn) == TableOptions.ColumnsOptIn;

            if (!string.IsNullOrEmpty(tableAttribute.TableName))
                resultInfo.TableName = tableAttribute.TableName;
            else
                resultInfo.TableName = targetType.Name;

            List<MemberInfo> members = new List<MemberInfo>();
            members.AddRange(targetType.GetFields().OfType<MemberInfo>());
            members.AddRange(targetType.GetProperties().OfType<MemberInfo>());

            foreach (MemberInfo member in members)
            {
                ColumnAttribute columnAttribute = member.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() as ColumnAttribute;
                ExcludeColumnAttribute excludeColumnAttribute = member.GetCustomAttributes(typeof(ExcludeColumnAttribute), false).FirstOrDefault() as ExcludeColumnAttribute;
                PrimaryKeyAttribute primaryKeyAtribute = member.GetCustomAttributes(typeof(PrimaryKeyAttribute), false).FirstOrDefault() as PrimaryKeyAttribute;

                if (excludeColumnAttribute != null)
                    continue; //Skips this member
                if (columnAttribute == null && optIn)
                    continue; //Skips this member


                string columnName;
                if (columnAttribute == null || string.IsNullOrEmpty(columnAttribute.ColumnName))
                    columnName = member.Name;
                else
                    columnName = columnAttribute.ColumnName;

                Type fieldOrPropertyType;
                var memberAsFieldInfo = member as FieldInfo;
                var memberAsPropertyInfo = member as PropertyInfo;

                if (memberAsFieldInfo != null)
                {
                    fieldOrPropertyType = memberAsFieldInfo.FieldType;
                }
                else
                {
                    Debug.Assert(memberAsPropertyInfo != null);
                    if (!memberAsPropertyInfo.CanWrite)
                    {
                        if (columnAttribute == null)
                            continue; //Skips this member
                        else
                            throw new IvyOrmException("Unable to use property " + memberAsPropertyInfo.Name + ", public setter is required. Remove the [" + typeof(ColumnAttribute) + "] attribute");
                    }

                    fieldOrPropertyType = memberAsPropertyInfo.PropertyType;
                    
                }

                    columnAttribute = columnAttribute ?? new ColumnAttribute();
                Type databaseType = columnAttribute.DatabaseType ?? fieldOrPropertyType;

                ValueConverterFactory converterFactory;
                if (columnAttribute.ConverterFactoryType == null)
                    converterFactory = null;
                else
                {
                    if (!typeof(ValueConverterFactory).IsAssignableFrom(columnAttribute.ConverterFactoryType))
                        throw new InvalidOperationException(columnAttribute.ConverterFactoryType.FullName + " is an invalid " + typeof(ValueConverterFactory));
                    converterFactory = (ValueConverterFactory)Activator.CreateInstance(columnAttribute.ConverterFactoryType);
                }
                bool primaryKey = primaryKeyAtribute != null;
                bool primaryKeyReturn = primaryKeyAtribute != null && ((primaryKeyAtribute.Options & PrimaryKeyOptions.ReturnKeyValueOnInsert) == PrimaryKeyOptions.ReturnKeyValueOnInsert);
                
                bool includeInInsert = primaryKeyAtribute == null || 
                                         ((primaryKeyAtribute.Options & PrimaryKeyOptions.IncludeInInsert) == PrimaryKeyOptions.IncludeInInsert) ||
                                         !((primaryKeyAtribute.Options & PrimaryKeyOptions.ReturnKeyValueOnInsert) == PrimaryKeyOptions.ReturnKeyValueOnInsert);

                Debug.Assert(!string.IsNullOrEmpty(columnName));
                Debug.Assert(databaseType != null);
                Debug.Assert(fieldOrPropertyType != null);
                Debug.Assert(member != null);

                resultInfo.Columns.Add(columnName, new IvyColumnInfo
                            {
                                ConverterFactory = converterFactory,
                                DatabaseType = databaseType,
                                FieldOrPropertyType = fieldOrPropertyType,
                                Member = member, 
                                PrimaryKey = primaryKey,
                                PrimaryKeyReturn = primaryKeyReturn,
                                IncludeInInsert = includeInInsert,
                                ColumnName = columnName,
                            });
            }

            if (resultInfo.Columns.Count == 0)
                throw new IvyOrmException("Unable to find any useable fields or properties");

            return resultInfo;
        }

        public Dictionary<string, IvyColumnInfo> Columns = new Dictionary<string, IvyColumnInfo>(StringComparer.OrdinalIgnoreCase);
        public string TableName { get; set; }
    }
}
